package msigraphx.gibbsSampler;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;

import com.rits.cloning.Cloner;


class Pair<Type1, Type2> implements Cloneable {
	public Type1 first;
	public Type2 second;

	public Pair() {}
	
	public Pair(Type1 first, Type2 second) {
		this.first = first;
		this.second = second;
	}

	//@SuppressWarnings("unchecked")
	public Object clone() throws CloneNotSupportedException {
		Pair<Type1, Type2> newPair = new Pair<Type1, Type2>();
		Cloner cloner = new Cloner();
		newPair.first = cloner.deepClone(first);
		newPair.second = cloner.deepClone(second);
		/*try {
			Method cloneMethodFirst = first.getClass().getMethod("clone");
			newPair.first = (Type1) cloneMethodFirst.invoke(first);
			Method cloneMethodSecond = first.getClass().getMethod("clone");
			newPair.second = (Type2) cloneMethodSecond.invoke(second);
		} catch (Exception e) {
			throw new CloneNotSupportedException("refletion exception while calling clone methods: "+e.toString());
		}*/
		
		return newPair;
	}
	
	public boolean equals(Object other) {
		if(!(other instanceof Pair)) {
			return false;
		}
		Pair<Type1,Type2> otherPair = (Pair<Type1,Type2>)other;
		return this.first.equals(otherPair.first) && this.second.equals(otherPair.second);
	}
}

class PairList<Type1, Type2> implements Cloneable {
	public List<Pair<Type1,Type2>> list;
	
	public PairList() {
		list = new Vector<Pair<Type1,Type2>>();
	}

	public Object clone() {
		return ((Vector<Pair<Type1,Type2>>)list).clone();
	}
}

public class CondDistr<Type> implements Cloneable {
	public PairList<RandomVariableValue<Type>, Map<List<RandomVariableValue<Type>>, Float> > val;
	
	public CondDistr() {
		val = new PairList<RandomVariableValue<Type>, Map<List<RandomVariableValue<Type>>, Float> >();
	}
	
	public Object clone() {
		return val.clone();
	}
	
	public boolean equals(Object other) {
		if(!(other instanceof CondDistr)) {
			return false;
		}
		CondDistr<Type> otherCondDistr = (CondDistr<Type>)other;
		return this.val.list.equals(otherCondDistr.val.list);
	}
	
	/**
	 * remove dependence from given random variable
	 * @param randVarIndex
	 */
	public void removeDependance(int randVarIndex) {
		val.list.remove(randVarIndex);
		Map<List<RandomVariableValue<Type>>, Float> tempMap;
		List<RandomVariableValue<Type>> tempList;
		Float prob;
		for(Pair<RandomVariableValue<Type>, Map<List<RandomVariableValue<Type>>, Float>> listRow : val.list) {
			tempMap = new HashMap<List<RandomVariableValue<Type>>, Float>();
			for(Map.Entry<List<RandomVariableValue<Type>>, Float> entry : listRow.second.entrySet()) {
				tempList = entry.getKey();
				tempList.remove(randVarIndex);
				prob = entry.getValue();
				if(tempMap.containsKey(tempList)) {
					prob += tempMap.get(tempList);
				}
				tempMap.put(tempList, prob);
			}
			listRow.second = new HashMap<List<RandomVariableValue<Type>>, Float>();
			listRow.second.putAll(tempMap);
		}
	}
	
	public void setDistrProb(RandomVariableValue<Type> thisVal, List<RandomVariableValue<Type>> parentsVal, Float prob) {
		for(Pair<RandomVariableValue<Type>, Map<List<RandomVariableValue<Type>>, Float>> entry : val.list) {
			if(entry.first.equals(thisVal)) {
				entry.second.put(parentsVal, prob);
				return;
			}
		}
		Map<List<RandomVariableValue<Type>>, Float> newMap = new HashMap<List<RandomVariableValue<Type>>, Float>();
		newMap.put(parentsVal, prob);
		val.list.add(new Pair<RandomVariableValue<Type>, Map<List<RandomVariableValue<Type>>, Float>> (thisVal, newMap));
	}
	
	@SuppressWarnings("unchecked")
	@Deprecated
	public CondDistr<Type> marginalize(List<RandomVariableValue<Type>> knownVariables) {
		CondDistr<Type> marginalized = (CondDistr<Type>)this.clone();
		
		//TODO
		
		return marginalized;
	}
	
	public VarDistr<Type> getDistrByAllDependantVar(List<RandomVariableValue<Type>> knownVariables) {
		VarDistr<Type> probDistr = new VarDistr<Type>();
		
		for(Pair<RandomVariableValue<Type>, Map<List<RandomVariableValue<Type>>, Float>> listRow : val.list) {
			Float probSum = 0f;
			boolean notContains = true;
			for(Map.Entry<List<RandomVariableValue<Type>>, Float> entry : listRow.second.entrySet()) {
				if(knownVariables.containsAll(entry.getKey())) {
					probSum += entry.getValue();
					notContains = false;
				}
			}
			if(notContains) {
				throw new IllegalArgumentException("distr map does not contain varVals list which would belong to: "+knownVariables.toString());
			}
			probDistr.addValue(listRow.first, probSum);
			/*if(listRow.second.containsKey(knownVariables)) {
				probDistr.addValue(listRow.first, listRow.second.get(knownVariables));
			}
			else {
				throw new IllegalArgumentException("distr map does not contain key: "+knownVariables.toString());
			}*/
		}
		
		return probDistr;
	}
	
	public boolean checkCondDistr() {
		RandomVariable<Type> randVar = null;
		Map<List<RandomVariableValue<Type>>, Float> sumCheckMap = new HashMap<List<RandomVariableValue<Type>>, Float>();
		List<RandomVariableValue<Type>> vals = new Vector<RandomVariableValue<Type>>();
		for(Pair<RandomVariableValue<Type>, Map<List<RandomVariableValue<Type>>, Float>> listRow : val.list) {
			if(randVar==null) {
				randVar = listRow.first.getRandVar();
			}
			else {
				if(!randVar.equals(listRow.first.getRandVar())) {
					System.out.println("checkCondDistr()   !randVar.equals(listRow.first.getRandVar())");
					return false;
				}
			}
			for(Map.Entry<List<RandomVariableValue<Type>>,Float> entry : listRow.second.entrySet()) {
				if(sumCheckMap.containsKey(entry.getKey())) {
					sumCheckMap.put(entry.getKey(), sumCheckMap.get(entry.getKey())+entry.getValue());
				}
				else {
					sumCheckMap.put(entry.getKey(), entry.getValue());
				}
			}
			if(!vals.contains(listRow.first)) {
				vals.add(listRow.first);
			}
		}
		if(!randVar.getAllowedValues().equals(vals)) {
			System.out.println("checkCondDistr()   !randVar.getAllowedValues().equals(vals)     vals = "+vals+"   randVar.getAllowedValues() = "+randVar.getAllowedValues());
			return false;
		}
		
		for(Map.Entry<List<RandomVariableValue<Type>>,Float> entry : sumCheckMap.entrySet()) {
			if(entry.getValue() < 0.999999f || entry.getValue() > 1.000000f) {
				System.out.println("checkCondDistr()   entry.getValue() != 1.0f    entry.getValue() = "+entry.getValue()+"   entry.getKey() = "+entry.getKey());
				return false;
			}
		}
		
		return true;
	}
}
